深入Netty(1) 强大的字节容器 您所在的位置:网站首页 Buf 深入Netty(1) 强大的字节容器

深入Netty(1) 强大的字节容器

2023-09-05 08:41| 来源: 网络整理| 查看: 265

(本文参考书籍netty in action编写)

什么是ByteBuf

说到网络编程,大家都知道,网络数据的基本单位总是字节。ByteBuf就是一个由netty实现的字节容器,英文全称应该是ByteBuffer(字节缓冲区)。

那么为什么不叫ByteBuffer呢?这自然是因为java nio包中,提供了同样功能的东西就叫ByteBuffer,所以自己实现的当然不能和官方的重名啦。但是官方实现的ByteBuffer使用起来过于复杂繁琐,因此netty就提供了自己的实现。

ByteBuf的工作原理

image.png

(如图就是一个8个字节大小的ByteBuf简易图,请忽略我对不齐的边框)

ByteBuf维护了两个不同的索引,就是rederIndex和writerIndex。一个用于读取,一个用于写入。 当你读取的时候readerIndex会自动递增你读取的字节数。同样,当你写入时,writerBuffer也会递增你写入的字节数。这两个索引默认都为0(如上图)。

image.png

我们看看源码就会发现 源码中有一堆read开头的方法和write开头的方法,其中大部分的read开头的方法都会使rederIndex递增,write开头的方法都会使writerIndex递增。

然后也还有很多get或者set开头的方法。他们一般不会递增索引,而是直接对他们做操作。我们还可以给ByteBuf设置一个初始的容量,如果超出了容量的操作就会抛出异常。

那么他们是怎么被使用的呢

image.png

如果学过java nio中的ByteBuffer,大家一定知道:他有两种模式:读模式和写模式,如果切换模式还得调用flip()函数。这是因为他内部其实只维护了一个索引。而netty中的ByteBuf维护了两个索引,自然就不需要flip函数了。

书归正题,一般情况下,一个被使用的ByteBuf的两个索引会如图这样分布(也就是writerIndex在后,readerIndex在前)。这样可把整个ByteBuf分成三个模块:可废弃字节(0和readerIndex之间),可读字节(readerIndex和writerIndex之间),可写字节(writerIndex之后)

可丢弃字节

其实他们就是以及读过的字节。所以自然是可以丢弃的(当然也有可能会重复读取之前读过的导致不能丢弃,这得看具体业务需求)

通过调用discardReadBytes()方法可以丢弃他们并且回收空间,把这些内存变为可写的。如下:

AbstractByteBuf类的实现 @Override public ByteBuf discardReadBytes() { //如果readerIndex是0 那不需要任何处理 if (readerIndex == 0) { // 判断是否可访问 ensureAccessible(); return this; } //正常情况都会不相等 if (readerIndex != writerIndex) { //把可读区域整体移到从0开始的地方 setBytes(0, this, readerIndex, writerIndex - readerIndex); //修改一下index writerIndex -= readerIndex; adjustMarkers(readerIndex); readerIndex = 0; } else { //读写相等 那只要改索引 ensureAccessible(); adjustMarkers(readerIndex); writerIndex = readerIndex = 0; } return this; }

image.png

虽然这个方法看上去很美好,但是实际上会导致大量的内存复制(就是上面代码中的setBytes方法)。而且这并不会确保可写分段的内容都被擦除了(不过没关系,反正接着写会覆盖他们)。

总之,一般来说,只有当内存非常宝贵的时候,才推荐使用这种方法。一般情况不推荐使用。他的代替方法是clear()

@Override public ByteBuf clear() { readerIndex = writerIndex = 0; return this; }

这个方法非常轻量级,仅仅是改变了索引,把所有区域都置为可写区域,并不会清除缓存中的内容,也不会导致内存复制。所以一般更推荐在合适的时候使用这个方法而不是discardReadBytes().

可读字节

可读字节就是写过但是还没读的数据。以read开头的方法都会读取当前readerIndex位置的字节,而以skip开头的一般都会跳过当前readerIndex位置的字节。两者都会改变当前的readerIndex的位置。

最常见的使用是readByte() 会读取当前字节。如果此时可读字节为0,则会报错。

下面是一个常见的使用方法:读取所有可读的字节。

while (buf.isReadable()){ System.out.println(buf.readByte()); }

其中isReadable()方法所返回的就是0,和writerIndex-readerIndex 中的较大值。

可写字节

可写分段指的是一个有未定义内容的,写入就绪的内存区域。以write开头的方法都会从当前writerIndex开始写数据,并将它增加已经写入的字节数。如果写操作的目标是ByteBuf,并且没有指定源索引的值,那么默认是从他的rederIndex开始写,所以源索引区的readerIndex也同样会增加相同的大小。这个调用如下:

writerBytes(ByteBuf dest);

如果写入超过容量的数据,则会报错。

如下是一个用随机整数值填充ByteBuf,直到它空间不足的例子。writeableBytes()方法在这里被采用来确定该缓冲区中是否还有足够的空间,它将返回等于(this.capacity(代表容量,后文还有讲到) - this.writerIndex)的可写字节数。

while (buf.writableBytes() >= 4){ buf.writeInt(random.nextInt()); } ByteBuf的操作 随机访问索引和顺序访问索引

在ByteBuf的实现类中,有的在字段里加了一个byte数组 有的则是一个int类型的字段,叫length。

无论如何,他们的其中一个作用就是实现获取到最后一个字节的索引。最后一个字节的索引一定是byte数组的length-1或者他维护的那个length-1。为了方便调用,ByteBuf有一个方法可以快速获得这个值,那就是capacity()

PooledByteBuf类的实现 protected int length; @Override public final int capacity() { return length; } UnpooledHeapByteBuf类中 byte[] array; @Override public int capacity() { return array.length; }

(还有更多种实现我就不一一列举了)

有了这个capcity()方法,我们就能很简单的实现随机访问索引。 比如说我想遍历每一个值,使用如下方式即可。

for (int i = 0;i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有